<template>
  <div v-show="globalConfig.show" id="kite-captcha-parent">
    <div id="kite-captcha-bg-img"
         :style="{backgroundImage: 'url('+globalConfig.renderData.boxBackgroundImage+')'}"></div>
    <div id="kite-captcha-box">
      <img v-show="globalConfig.loading && globalConfig.renderData.loadingImage" id="kite-captcha-loading"
           :src="globalConfig.renderData.loadingImage"
           alt="loading">
      <div v-if="globalConfig.renderData.type === 'SLIDER'" id="kite-captcha" :class="globalConfig.captchaClass"
           class="kite-captcha-slider animate__animated">
        <div class="kite-slider-tips">
          <slot name="title">
            <span>{{ $t('captcha.dragSlider') }}</span>
          </slot>
        </div>
        <div class="kite-captcha-content">
          <div id="kite-captcha-bg-img-box" class="kite-captcha-bg-img-box">
            <img id="kite-slider-bg-img" :src="globalConfig.renderData.backgroundImage" alt=""/>
            <canvas id="kite-captcha-slider-bg-canvas"></canvas>
          </div>
          <div id="kite-slider-move-img-box" :style="globalConfig.sliderImgStyles" class="kite-slider-move-img-box">
            <img id="kite-captcha-slider-move-img" :src="globalConfig.renderData.templateImage" alt="">
          </div>
          <div id="kite-captcha-tips" :class="globalConfig.tipsClass" class="kite-captcha-tips">{{
              globalConfig.tipsContent
            }}
          </div>
        </div>
        <div class="kite-slider-move">
          <div class="kite-slider-move-track">
            <div id="kite-captcha-slider-move-track-mask"
                 :style="globalConfig.moveTracMaskStyles"></div>
            <div class="kite-slider-move-shadow"></div>
          </div>
          <div id="kite-captcha-slider-move-btn"
               :style="globalConfig.moveBtnStyles"
               class="kite-slider-move-btn"
               @mousedown="sliderDown($event)"
               @touchstart="sliderDown($event)">
          </div>
        </div>
      </div>
      <div v-else-if="globalConfig.renderData.type === 'ROTATE'" id="kite-captcha" :class="globalConfig.captchaClass"
           class="kite-captcha-slider kite-captcha-rotate animate__animated">
        <div class="kite-slider-tips">
          <slot name="title">
            <span>{{ $t('captcha.dragSlider') }}</span>
          </slot>
        </div>
        <div class="kite-captcha-content">
          <div id="kite-captcha-bg-img-box" class="kite-captcha-bg-img-box">
            <img id="kite-slider-bg-img" :src="globalConfig.renderData.backgroundImage" alt=""/>
            <canvas id="kite-captcha-slider-bg-canvas"></canvas>
          </div>
          <div id="kite-slider-move-img-box" class="kite-slider-move-img-box">
            <img id="kite-captcha-slider-move-img" :src="globalConfig.renderData.templateImage"
                 :style="globalConfig.rotateImgStyles" alt="">
          </div>
          <div id="kite-captcha-tips" :class="globalConfig.tipsClass" class="kite-captcha-tips">{{
              globalConfig.tipsContent
            }}
          </div>
        </div>
        <div class="kite-slider-move">
          <div class="kite-slider-move-track">
            <div id="kite-captcha-slider-move-track-mask"
                 :style="globalConfig.moveTracMaskStyles"></div>
            <div class="kite-slider-move-shadow"></div>
          </div>
          <div id="kite-captcha-slider-move-btn"
               :style="globalConfig.moveBtnStyles"
               class="kite-slider-move-btn"
               style="background-image: url('');"
               @mousedown="sliderDown($event)"
               @touchstart="sliderDown($event)">
          </div>
        </div>
      </div>
      <div v-else-if="globalConfig.renderData.type === 'CONCAT'" id="kite-captcha" :class="globalConfig.captchaClass"
           class="kite-captcha-slider kite-captcha-concat animate__animated">
        <div class="kite-slider-tips">
          <slot name="title">
            <span>{{ $t('captcha.dragSlider') }}</span>
          </slot>
        </div>
        <div class="kite-captcha-content">
          <div id="kite-captcha-slider-concat-img" :style="globalConfig.concatImgStyles"
               class="kite-captcha-slider-concat-img">
            <img id="kite-captcha-slider-concat-slider-img" alt=""/>
          </div>
          <div id="kite-captcha-slider-concat-bg-img" :style="globalConfig.concatBgImgStyles"
               class="kite-captcha-slider-concat-bg-img"></div>
          <div id="kite-captcha-tips" :class="globalConfig.tipsClass" class="kite-captcha-tips">{{
              globalConfig.tipsContent
            }}
          </div>
        </div>
        <div class="kite-slider-move">
          <div class="kite-slider-move-track">
            <div id="kite-captcha-slider-move-track-mask"
                 :style="globalConfig.moveTracMaskStyles"
                 style="border-color: rgb(2, 152, 248); background-color: rgb(137, 210, 255);"></div>
            <div class="kite-slider-move-shadow"></div>
          </div>
          <div id="kite-captcha-slider-move-btn"
               :style="globalConfig.moveBtnStyles"
               class="kite-slider-move-btn"
               style="background-image: url('');"
               @mousedown="sliderDown($event)"
               @touchstart="sliderDown($event)">
          </div>
        </div>
      </div>
      <div v-else-if="globalConfig.renderData.type === 'WORD_IMAGE_CLICK'" id="kite-captcha"
           :class="globalConfig.captchaClass"
           class="kite-captcha-slider kite-captcha-work-click animate__animated">
        <div class="kite-work-click-tips">
          <slot name="title">
            <span>{{ $t('captcha.clickTips') }}</span>
          </slot>
          <img id="kite-work-click-tip-img" :src="globalConfig.wordImgData.tipImage" class="kite-work-click-tip-img">
        </div>
        <div class="kite-captcha-content" @click="wordClick">
          <div class="kite-captcha-bg-img-box">
            <img id="kite-captcha-work-click-bg-img" :src="globalConfig.wordImgData.backgroundImage"
                 class="kite-captcha-work-click-bg-img">
            <canvas id="kite-captcha-slider-bg-canvas"></canvas>
            <div id="kite-bg-img-click-mask">
              <span
                  v-for="item in globalConfig.clickData"
                  :key="'click_' + item.count"
                  :style="{left: item.left, top: item.top }"
                  class="click-span"
              >{{ item.count }}</span>
            </div>
          </div>
          <div id="kite-captcha-tips" :class="globalConfig.tipsClass" class="kite-captcha-tips">{{
              globalConfig.tipsContent
            }}
          </div>
        </div>
      </div>

    </div>

    <div class="kite-captcha-slider-footer">
      <slot name="footer">
        <img v-if="globalConfig.renderData.logoImage" :src="globalConfig.renderData.logoImage"
             alt="captcha-logo"
             class="kite-captcha-logo"/>
        <div class="captcha-tool-button">
          <div v-show="showClose" :title="$t('captcha.closeTips')" class="kite-close-btn" @click="close"></div>
          <div :title="$t('captcha.refreshTips')" class="kite-refresh-btn" @click="refresh"></div>
          <div :title="$t('captcha.infoTips')" class="kite-info-btn"></div>
        </div>
      </slot>
    </div>
  </div>

</template>

<script lang="ts" setup>
import {nextTick, onMounted, reactive, ref, watch} from 'vue'
import {CaptchaMatchingData, CaptchaProps, CaptchaRenderData, CaptchaTrack, ClickDataItem, langs} from "../../index";
import {useI18n} from 'vue-i18n'

// 检查是否安装了 vue-i18n
let $t: (key: string, params?: any) => string;
try {
  const i18n = useI18n()
  $t = i18n.t
} catch (e) {
  // 如果未安装 vue-i18n，则使用默认中文翻译
  $t = (key: string, params?: any) => {

    const translations: { [key: string]: string } = {
      'captcha.dragSlider': langs.zh_cn.captcha.dragSlider,
      'captcha.clickTips': langs.zh_cn.captcha.clickTips,
      'captcha.closeTips': langs.zh_cn.captcha.closeTips,
      'captcha.refreshTips': langs.zh_cn.captcha.refreshTips,
      'captcha.infoTips': langs.zh_cn.captcha.infoTips,
      'captcha.expired': langs.zh_cn.captcha.expired,
      'captcha.failed': langs.zh_cn.captcha.failed,
      'captcha.success': langs.zh_cn.captcha.success
    }
    let translation = translations[key] || key
    if (params) {
      Object.keys(params).forEach(param => {
        translation = translation.replace(`{${param}}`, params[param])
      })
    }
    return translation
  }
}

defineOptions({
  name: 'KiteCaptcha'
})

const props = withDefaults(defineProps<CaptchaProps & {
  modelValue: number | string
}>(), {
  scale: 1,
  loadingAnimation: 'slide',
  showClose: true,
  showTool: true,
  matchingResultCodeProps: () => ({
    expired: 4000,
    failed: 4001,
    success: 200
  })
});


// 定义事件发射器
const emit = defineEmits<{
  (e: 'update:modelValue', value: any): void
  (e: 'render-success', success: boolean): void
  (e: 'matching-data', result: any): void
}>()

// 全局数据状态
const globalConfig = reactive({
  renderData: {} as CaptchaRenderData,
  matchingData: {
    id: '',
    data: {
      trackList: [] as CaptchaTrack[]
    }
  } as CaptchaMatchingData,
  startDate: new Date,
  endDate: new Date,
  startX: 0,
  startY: 0,
  end: 0,
  moveX: 0,
  movePercent: 0,
  captchaClass: '',
  moveTracMaskStyles: {
    width: ''
  },
  // 移动按钮样式
  moveBtnStyles: {
    transform: 'translateX(0)',
    backgroundImage: ''

  },
  // 滑动样式
  sliderImgStyles: {
    transform: 'translateX(0)'
  },
  // 旋转样式
  rotateImgStyles: {
    transform: 'rotate(0deg)'
  },
  // 滚动还原样式
  concatImgStyles: {
    height: '',
    backgroundImage: '',
    backgroundPosition: '0px 0px'
  },
  // 滚动还原背景图片
  concatBgImgStyles: {
    backgroundImage: ''
  },
  // 文字点击数据
  wordImgData: {
    backgroundImage: '',
    tipImage: ''
  },
  // 文字点击数据
  clickData: [{count: 0, left: 0, top: 0}] as ClickDataItem[],
  clickCount: 0,
  show: true,
  loading: false,
  tipsContent: '',
  tipsClass: '',
  activeTime: {
    startTime: 0,
    endTime: 0
  },
  renderSuccess: false,
});

// 用于跟踪事件监听器状态
const isTracking = ref(false)

// 组件挂载时初始化
onMounted(() => {
  gen()
})
watch(() => props.modelValue, (value: string | number) => {
  console.log('matchingResultCode', value)
  if (value) {
    globalConfig.activeTime.endTime = new Date().getTime()
    if (value == props.matchingResultCodeProps.expired) {
      tipsError($t('captcha.expired'))
      setTimeout(async (): Promise<void> => await refresh(), 1000)
      return
    }
    if (value == props.matchingResultCodeProps.success) {
      tipsSuccess()
      return
    }
    if (value == props.matchingResultCodeProps.failed) {
      tipsError(null)
      setTimeout(async (): Promise<void> => await refresh(), 1000)
    }
    emit('update:modelValue', '')
  }

})

/**
 * 初始化验证码容器
 */
const init = (): void => {
  let bgElement = document.getElementById('kite-captcha-bg-img-box')
  if (!bgElement) {
    bgElement = document.getElementById('kite-captcha-slider-concat-bg-img')
  }
  const sliderMoveBtnElement = document.getElementById('kite-captcha-slider-move-btn')
  if (bgElement && sliderMoveBtnElement) {
    globalConfig.matchingData.data.bgImageWidth = bgElement.clientWidth
    globalConfig.matchingData.data.bgImageHeight = bgElement.clientHeight
    globalConfig.end = globalConfig.matchingData.data.bgImageWidth - sliderMoveBtnElement.clientWidth
    if (globalConfig.renderData.type === 'ROTATE') {
      const bgImageSize = globalConfig.matchingData.data.bgImageHeight
      globalConfig.matchingData.data.sliderImageWidth = bgImageSize
      globalConfig.matchingData.data.sliderImageHeight = bgImageSize
      globalConfig.matchingData.data.bgImageWidth = globalConfig.end
    }
  }
};

/**
 * 生成滑动验证图片
 */
const gen = async (): Promise<void> => {
  globalConfig.renderSuccess = false
  try {
    setTimeout(async (): Promise<void> => await render(), 1000)
  } catch (error) {
    console.error('验证码获取失败:', error)
    globalConfig.loading = false
    tipsError('验证码加载失败')
  }
};
/**
 * 渲染验证码
 */
const render = async (): Promise<void> => {
  globalConfig.show = true
  globalConfig.loading = true
  globalConfig.renderData = await props.renderDataApi();
  globalConfig.matchingData.id = globalConfig.renderData.id
  await nextTick(() => {
    const captchaParent = document.getElementById('kite-captcha-parent')
    captchaParent?.style.setProperty('--scale', props.scale + '')
  })
  reset()

  globalConfig.loading = false
  globalConfig.moveBtnStyles.backgroundImage = 'url(' + globalConfig.renderData.sliderButtonImage + ')'
  if (props.backgroundImage) {
    globalConfig.renderData.boxBackgroundImage = props.backgroundImage
  }
  let sliderElement = document.getElementById('kite-captcha-slider-move-img')
  if (!sliderElement) {
    sliderElement = document.getElementById('kite-captcha-slider-concat-img')
  }
  if (globalConfig.renderData.type !== 'WORD_IMAGE_CLICK') {
    if (sliderElement) {
      globalConfig.matchingData.data.sliderImageWidth = sliderElement.clientWidth
      globalConfig.matchingData.data.sliderImageHeight = sliderElement.clientHeight
    }
  }

  if (globalConfig.renderData.type === 'CONCAT') {
    globalConfig.concatBgImgStyles.backgroundImage = 'url(' + globalConfig.renderData.backgroundImage + ')'
    const backgroundImageHeight = globalConfig.renderData.backgroundImageHeight
    const height = ((backgroundImageHeight - globalConfig.renderData.data?.randomY) / backgroundImageHeight) * 180 * props.scale
    globalConfig.concatImgStyles.backgroundImage = 'url(' + globalConfig.renderData.backgroundImage + ')'
    globalConfig.concatImgStyles.height = height + 'px'
    const bgImg = document.getElementById('kite-captcha')
    if (bgImg) {
      globalConfig.matchingData.data.bgImageWidth = bgImg.clientWidth
      globalConfig.matchingData.data.bgImageHeight = bgImg.clientHeight
    }

  } else if (globalConfig.renderData.type === 'WORD_IMAGE_CLICK') {
    globalConfig.wordImgData.backgroundImage = globalConfig.renderData.backgroundImage
    globalConfig.wordImgData.tipImage = globalConfig.renderData.templateImage
  }

  globalConfig.startDate = new Date()
  console.log('globalConfig\n', JSON.stringify(globalConfig))
  setTimeout(() => {
    globalConfig.renderSuccess = true
    emit('render-success', globalConfig.renderSuccess)
  }, 1000)
};


const sendMatchingData = (): void => {
  if (globalConfig.renderData.type === 'WORD_IMAGE_CLICK') {
    const clickContent = document.getElementById('kite-bg-img-click-mask')
    const tipContent = document.getElementById('kite-work-click-tip-img')
    if (clickContent) {
      globalConfig.matchingData.data.bgImageHeight = clickContent.clientHeight
      globalConfig.matchingData.data.bgImageWidth = clickContent.clientWidth
    }
    if (tipContent) {
      globalConfig.matchingData.data.sliderImageWidth = tipContent.clientWidth
      globalConfig.matchingData.data.sliderImageHeight = tipContent.clientHeight
    }
  }

  globalConfig.matchingData.data.startSlidingTime = formatDate(globalConfig.startDate)
  globalConfig.matchingData.data.endSlidingTime = formatDate(globalConfig.endDate)
  emit('matching-data', globalConfig.matchingData)

};
/**
 * 刷新
 */
const refresh = async (): Promise<void> => {
  if (!globalConfig.renderSuccess) {
    return
  }
  await nextTick(() => {
    globalConfig.captchaClass = `animate__${props.loadingAnimation}OutRight`
    gen()
  })

}
/**
 * 重置
 */
const reset = (): void => {
  globalConfig.moveBtnStyles.transform = 'translateX(0)'
  globalConfig.moveTracMaskStyles.width = ''
  globalConfig.sliderImgStyles.transform = 'translateX(0)'
  globalConfig.rotateImgStyles.transform = 'rotate(0deg)'
  globalConfig.concatImgStyles.backgroundPosition = '0px 0px'
  globalConfig.concatImgStyles.backgroundImage = ''
  globalConfig.concatImgStyles.height = ''
  globalConfig.wordImgData.backgroundImage = ''
  globalConfig.wordImgData.tipImage = ''
  globalConfig.end = 0
  globalConfig.startX = 0
  globalConfig.startY = 0
  globalConfig.moveX = 0
  globalConfig.movePercent = 0
  globalConfig.startDate = new Date()
  globalConfig.endDate = new Date()
  globalConfig.clickCount = 0
  globalConfig.clickData = []
  globalConfig.tipsContent = ''
  globalConfig.tipsClass = ''
  globalConfig.captchaClass = ''
  globalConfig.matchingData.data.trackList = [] as CaptchaTrack[]
  // 移除所有事件监听器
  if (isTracking.value) {
    window.removeEventListener('mousemove', move)
    window.removeEventListener('mouseup', up)
    window.removeEventListener('touchmove', move)
    window.removeEventListener('touchend', up)
    window.removeEventListener('mousemove', wordMove)
    isTracking.value = false
  }
  globalConfig.captchaClass = `animate__${props.loadingAnimation}InLeft`
};
/**
 * 关闭验证码容器
 */
const close = (): void => {
  globalConfig.show = false
};

/**
 * 日期格式化
 * @param date 日期对象
 */
const formatDate = (date: Date): string => {
  const dt = date
  const y = dt.getFullYear()
  const m = (dt.getMonth() + 1 + '').padStart(2, '0')
  const d = (dt.getDate() + '').padStart(2, '0')
  const hh = (dt.getHours() + '').padStart(2, '0')
  const mm = (dt.getMinutes() + '').padStart(2, '0')
  const ss = (dt.getSeconds() + '').padStart(2, '0')
  const sss = (dt.getMilliseconds() + '').padStart(2, '0')
  return `${y}-${m}-${d} ${hh}:${mm}:${ss}.${sss}`
};

const tipsError = (msg: string | null): void => {
  globalConfig.tipsContent = msg ? msg : $t('captcha.failed');
  globalConfig.tipsClass = 'tips-error tips-on';
};

const tipsSuccess = (): void => {
  const sec = (globalConfig.activeTime.endTime - globalConfig.activeTime.startTime) / 1000
  globalConfig.tipsContent = $t('captcha.success', {sec: sec.toFixed(1)})
  globalConfig.tipsClass = 'tips-success tips-on'
};

/*
  * 滑动图片鼠标按下
  */
const sliderDown = (event: any): void => {
  init()
  globalConfig.activeTime.startTime = new Date().getTime();
  const targetTouches = event.originalEvent ? event.originalEvent.targetTouches : event.targetTouches
  let startX = event.pageX
  let startY = event.pageY
  if (startX === undefined) {
    startX = Math.round(targetTouches[0].pageX)
    startY = Math.round(targetTouches[0].pageY)
  }
  globalConfig.startX = startX
  globalConfig.startY = startY

  const pageX = globalConfig.startX
  const pageY = globalConfig.startY
  const track = {
    x: pageX - startX,
    y: pageY - startY,
    type: 'down',
    t: (new Date().getTime() - globalConfig.startDate.getTime())
  } as CaptchaTrack
  globalConfig.matchingData.data.trackList.push(track)
  // 添加事件监听器
  window.addEventListener('mousemove', move)
  window.addEventListener('mouseup', up)
  window.addEventListener('touchmove', move, {passive: false})
  window.addEventListener('touchend', up)
  isTracking.value = true
};

/**
 * 移动
 * @param event
 */
const move = (event: any): void => {
  if (event instanceof TouchEvent) {
    event = event.touches[0]
  }
  const pageX = Math.round(event.pageX)
  const pageY = Math.round(event.pageY)
  const startX = globalConfig.startX
  const startY = globalConfig.startY
  const end = globalConfig.end
  const bgImageWidth = globalConfig.matchingData.data.bgImageWidth
  let moveX = pageX - startX
  const track = {
    x: pageX - startX,
    y: pageY - startY,
    type: 'move',
    t: (new Date().getTime() - globalConfig.startDate.getTime())
  } as CaptchaTrack
  globalConfig.matchingData.data.trackList.push(track)
  if (moveX < 0) {
    moveX = 0
  } else if (moveX > end) {
    moveX = end
  }
  globalConfig.moveX = moveX
  globalConfig.movePercent = moveX / bgImageWidth

  globalConfig.moveTracMaskStyles.width = moveX + 'px'
  globalConfig.moveBtnStyles.transform = 'translateX(' + moveX + 'px)'
  globalConfig.sliderImgStyles.transform = 'translateX(' + moveX + 'px)'
  globalConfig.rotateImgStyles.transform = 'rotate(' + (moveX / (end / 360)) + 'deg)'
  globalConfig.concatImgStyles.backgroundPosition = (moveX + 'px 0px')
  console.log('move', JSON.stringify(track))
}

/**
 * 鼠标松下,进行验证
 * @param event
 */
const up = (event: any): void => {
  globalConfig.endDate = new Date()
  // 移除事件监听器
  window.removeEventListener('mousemove', move)
  window.removeEventListener('mouseup', up)
  window.removeEventListener('touchmove', move)
  window.removeEventListener('touchend', up)
  isTracking.value = false
  if (event instanceof TouchEvent) {
    event = event.changedTouches[0]
  }
  const pageX = Math.round(event.pageX)
  const pageY = Math.round(event.pageY)
  const startX = globalConfig.startX
  const startY = globalConfig.startY
  const track = {
    x: pageX - startX,
    y: pageY - startY,
    type: 'up',
    t: (new Date().getTime() - globalConfig.startDate.getTime())
  } as CaptchaTrack
  globalConfig.matchingData.data.trackList.push(track)
  console.log('up', JSON.stringify(track))
  sendMatchingData()
};
/**
 * 点击文字
 */
const wordClick = (event: any): void => {

  globalConfig.clickCount++
  if (globalConfig.clickCount > 4) {
    return;
  }
  if (globalConfig.clickCount === 1) {
    globalConfig.activeTime.startTime = new Date().getTime()
    globalConfig.startDate = new Date()
    // move 轨迹
    window.addEventListener('mousemove', wordMove)
    isTracking.value = true
  }
  const track = {
    x: event.offsetX,
    y: event.offsetY,
    type: 'click',
    t: (new Date().getTime() - globalConfig.startDate.getTime())
  } as CaptchaTrack
  globalConfig.matchingData.data.trackList.push(track)
  const left = event.offsetX - 10
  const top = event.offsetY - 10
  globalConfig.clickData.push({
    count: globalConfig.clickCount,
    left: left + 'px',
    top: top + 'px'
  })

  if (globalConfig.clickCount === 4) {
    // 校验
    globalConfig.endDate = new Date()
    window.removeEventListener('mousemove', wordMove)
    isTracking.value = false
    sendMatchingData()
  }
}
/**
 * 文字点击移动
 * @param event
 */
const wordMove = (event: any): void => {
  if (event instanceof TouchEvent) {
    event = event.touches[0]
  }
  const track = {
    x: event.offsetX,
    y: event.offsetY,
    t: (new Date().getTime() - globalConfig.startDate.getTime()),
    type: 'move'
  } as CaptchaTrack
  globalConfig.matchingData.data.trackList.push(track)
}

defineExpose({
  refresh
})
</script>

<style lang="scss">
@use "index";
@import '../../../node_modules/animate.css/animate.min.css';
</style>
